/*
* This file is the main code of Timeless
* Copyright (C) <2019>  <alkeon> [alkeon@autistici.org]
*
* Texdi is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Texdi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with timeless.  If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "datalite.h"
#include "json.hpp"
#include <sqlite3.h>
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
using json = nlohmann::json;
static int callback(void *NotUsed, int argc, char **argv, char **azColName){ 
	return 0;
}

int datalite::create(){	
	sqlite3 * sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	int result = 0;

	if (!rc){
		char *zErrMsg = 0;
		string sql = "DROP TABLE IF EXISTS NEWS; \
			CREATE TABLE NEWS(    \
			LINK TEXT NOT NULL, \
			TITLE TEXT NOT NULL, \
			DESCRIPTION TEXT NOT NULL, \
			MARKED INTEGER, \
			UNIQUE (LINK,TITLE,DESCRIPTION) \
			);";
		rc = sqlite3_exec(sq, sql.c_str(), callback, 0, &zErrMsg);
		if (rc == SQLITE_OK)
			result = SQLITE_OK;
		else{
			sqlite3_free(zErrMsg);
			result = rc;
		}
	} else
		result = 1;
	
	sqlite3_close(sq);
	string channel = _file + "_channel";
	rc = sqlite3_open(channel.c_str(), &sq);
	result = 0;

	if (!rc){
		char *zErrMsg = 0;
		string sql = "DROP TABLE IF EXISTS CHANNEL; \
			CREATE TABLE CHANNEL(    \
			URL TEXT NOT NULL UNIQUE, \
			TITLE TEXT NOT NULL UNIQUE \
			);";
		rc = sqlite3_exec(sq, sql.c_str(), callback, 0, &zErrMsg);
		if (rc == SQLITE_OK)
			result = SQLITE_OK;
		else{
			sqlite3_free(zErrMsg);
			result = rc;
		}
	} else
		result = 1;
	
	sqlite3_close(sq);
	return result;
}

datalite::datalite(const string& file): _file(file){
	_position = 1;
	_last_position = 1;
	int result = create();
	if(result != 0)
		throw datalite::Invalid("Couldn't create database.\n ERROR CODE: " + to_string(result));
}

/*
 * Return:
 * 0 : failed
 * 1 : OK
 * 2 : inserted before
 */
int datalite::insert_row(const string& link, const string& title,
	const string& description){
	
	sqlite3 * sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	int result = 0;

	if (!rc){
		sqlite3_stmt * res;
		string sql = "INSERT INTO NEWS VALUES ('";
		sql += link + "', '" + title + "', '" + description + "', 0);";
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
		if (rc == SQLITE_OK) {
			int step = sqlite3_step(res);
			if(step == SQLITE_CONSTRAINT)
				result = 2;
			else if(step == SQLITE_DONE)
				result = 1;
			else if(step == SQLITE_OK)
				result = 1;
		}else
			result = 2;
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return result;
}

/*
 * Return:
 * 0 : failed
 * 1 : OK
 * 2 : inserted before
 */
int datalite::bulk_insert(const vector<string>& links, const vector<string>& titles,
	const vector<string>& descriptions){

	int result = 0;
	if(links.size() > 0){
		sqlite3 * sq;
		int rc = sqlite3_open(_file.c_str(), &sq);

		if (!rc){
			sqlite3_stmt * res;
			string sql = "INSERT OR IGNORE INTO NEWS VALUES ('";
			sql += links[0] + "', '" + titles[0] + "', '"+ descriptions[0] +"', 0)";
			for(unsigned i = 1; i < links.size(); ++i)
				sql += ",('" + links[i] + "', '" + titles[i] + "', '"+ descriptions[i] +"', 0)";
			
			sql+= ";";
			rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
			if (rc == SQLITE_OK) 
				sqlite3_step(res);
			else
				result = 2;
			sqlite3_finalize(res);
		}
		sqlite3_close(sq);
	}

	return result;
}

int datalite::delete_news(){

	sqlite3 * sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	int result = 0;

	if (!rc){
		sqlite3_stmt * res;
		string sql = "DELETE FROM NEWS WHERE MARKED=0;";
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
		if (rc == SQLITE_OK) {
			int step = sqlite3_step(res);
			if(step == SQLITE_CONSTRAINT)
				result = 2;
			else if(step == SQLITE_DONE)
				result = 1;
			else if(step == SQLITE_OK)
				result = 1;
		}else
			result = 2;
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return result;
}

void datalite::show_news(){
	string sql = "SELECT LINK, TITLE, DESCRIPTION, MARKED, rowid FROM NEWS "
		"ORDER BY rowid DESC;";
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	vector<int64_t> vector_positions;
	if (!rc){
		sqlite3_stmt *res;
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK) {

			int step = sqlite3_step(res);
			int option = 4;
			int iterations = 0;
			vector<long long> rowids;
			bool repeat = false;
			int i = 0;

			do{

				if(!repeat){
					rowids = vector<long long>();
					i = 0;
				}

				while (step == SQLITE_ROW && i < 3 && !repeat){

					const char * rowid = (char *) sqlite3_column_text(res,4);
					if(rowid != NULL){
						string rowid_s(rowid);
						rowids.push_back(stoll(rowid_s));
					}
					step = sqlite3_step(res);
					++i;

				}

				int aux = 0;
				if(repeat) repeat = false;

				for(vector<long long>::iterator it = rowids.begin(); it != rowids.end(); ++it){
					cout << "\n" << aux << "----------------" << endl;
					cout << get_one_new(*it) << endl;
					++aux;
				}

				if(i >= 3){
					cout << "\nUse news number to un/mark one of it" << endl;
					cout << "Do you want more news" << endl;
					cout << "3.- Yes" << endl;
					cout << "4.- No" << endl;
					cin >> option;
					cout << option << endl;
					if(option < 3 && option >= 0){
						vector_positions.push_back(rowids[option]);
						repeat = true;
						option = 3;
						
					}else if(option > 4 or option < 0){
						repeat = true;
						option = 3;
					}
					
				} else
					option = 3;
				++iterations;
			}while(option == 3 && step == SQLITE_ROW);

		}
		sqlite3_finalize(res);
	}

	sqlite3_close(sq);
	for(unsigned i = 0; i < vector_positions.size(); ++i)
		set_mark(vector_positions[i]);
}

int datalite::get_mark(int64_t position){
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link = "0";

	if (!rc){
		
		sqlite3_stmt *res;
		string sql = "SELECT MARKED FROM NEWS WHERE rowid=" + to_string(position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK){

			sqlite3_step(res);
			const char * marked = (char *) sqlite3_column_text(res,0);
			if(marked != NULL) link = string(marked);

		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return stoi(link);
}

int datalite::set_mark(int64_t position){

	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	int result = 0;

	if (!rc){
		sqlite3_stmt *res;
		string sql = "UPDATE NEWS SET MARKED=" + to_string((get_mark(position) 
			+ 1) % 2) + " where rowid=" + to_string(position);

		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
		
		if(rc == SQLITE_OK){
			sqlite3_step(res);
			result = SQLITE_OK;
		} else result = rc;

		sqlite3_finalize(res);
	}

	sqlite3_close(sq);
	return result;
}

/*
 * Error: line.size = 0
 * Success: line.size > 0
 *
 */
string datalite::get_new_line(){

	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link;
	if (!rc){
		
		sqlite3_stmt *res;
		string sql = "SELECT LINK FROM NEWS WHERE rowid=" + to_string(_position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK) {
			sqlite3_step(res);

			const char * retri = (char *) sqlite3_column_text(res,0);
			if(retri != NULL){
				
				link = string(retri);
				++_position;
			}
		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return link;
}

/*
 * Error: line.size = 0
 * Success: line.size > 0
 *
 */
string datalite::get_new_channel(){

	std::ifstream in(_file + "_channel");

	json j = json::parse(in);
	string channel = j["urls"].at(_position_channel);
	++_position_channel;
	return channel;
}

/*
 * Error: line.size = 0
 * Success: line.size > 0
 *
 */
string datalite::get_link(int64_t position){
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link;

	if (!rc){
		
		sqlite3_stmt *res;
		string sql = "SELECT LINK FROM NEWS WHERE rowid=" + to_string(position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK) {
			sqlite3_step(res);
			const char * retri = (char *) sqlite3_column_text(res,0);

			if(retri != NULL) link = string(retri);

		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return link;
}

/*
 * Error: line.size = 0
 * Success: line.size > 0
 *
 */
string datalite::get_channel(int64_t position){
	std::ifstream in(_file + "_channel");
	json j = json::parse(in);

	return j["urls"].at(position);
}

bool datalite::is_valid_line(){
	bool valid = false;
	if(_position <= _last_position) valid = true;
	else {
		_last_position = get_last_position();
 		if(_position <= _last_position) valid = true;
		else valid = false;
	}
	return valid;
}

bool datalite::is_valid_channel(){
	bool valid = false;
	if(_position_channel < _last_position_channel) valid = true;
	else {
		_last_position_channel = get_last_channel();
 		if(_position_channel <= _last_position_channel) valid = true;
		else valid = false;
	}
	return valid;
}

/*
 * Return rowid if worked and -1 if doesn't
 *
 */
int64_t datalite::get_last_position(){
	if(_position > _last_position){
		sqlite3 *sq;
		int rc = sqlite3_open(_file.c_str(), &sq);

		if (!rc){
			sqlite3_stmt *res;
			string sql = "SELECT rowid FROM NEWS WHERE rowid=(SELECT MAX(rowid)"
				" FROM NEWS)";
			rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

			if (rc == SQLITE_OK) {
				sqlite3_step(res);

				const char *retri = (char *) sqlite3_column_text(res,0);
				if(retri != NULL){
					string text(retri);
					_last_position = stoll(text);

				}
			}
			sqlite3_finalize(res);
		}
		sqlite3_close(sq);
	}
	return _last_position;
}

/*
 * Return rowid if worked and -1 if doesn't
 *
 */
int64_t datalite::get_last_channel(){
	if(_position_channel > _last_position_channel){
	
		std::ifstream in(_file + "_channel");
		json j = json::parse(in);

		int i = -1;

		for(auto json_building : j["urls"]){
			++i;
		}
	
		_last_position_channel = i;
	}
	return _last_position_channel;
}

void datalite::set_position(int64_t position){
	if(position <= _last_position) _position = position;
	else {
		_last_position = get_last_position();
 		if(position <= _last_position) _position = position;
		else throw datalite::Invalid("Rowid greater than table rowid");

	}
}

datalite& datalite::operator=(datalite& src){

	if (this != &src) { 
		_file = src.get_file();
		_position = src.get_position();
		_last_position = src.get_last_position();
	}
	return *this;
}

string datalite::get_title(const int64_t& position){
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link;
	if (!rc){
		sqlite3_stmt *res;
		string sql = "SELECT TITLE FROM COB WHERE rowid=" + to_string(position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
		if (rc == SQLITE_OK) {
		
			sqlite3_step(res);
			const char * title = (char *) sqlite3_column_text(res,0);
		if(title != NULL)
			link = string(title);
		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return link;
}

string datalite::get_metainfo(const int64_t& position){
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link;
	if (!rc){
		sqlite3_stmt *res;
		string sql = "SELECT DESCRIPTION FROM COB WHERE rowid=" + to_string(position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);
		if (rc == SQLITE_OK) {
		
			sqlite3_step(res);
			const char * description = (char *) sqlite3_column_text(res,0);
			if(description != NULL)
				link = string(description);
		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return link;
}

void datalite::insert_datalite(datalite& db){
	int64_t position = db.get_position();
	db.set_position(1);
	cout << "Wait until insertion stop" << endl;
	while(db.is_valid_line()){
		string title = db.get_title(db.get_position());
		string metainfo = db.get_metainfo(db.get_position());
		insert_row(db.get_new_line(), title, metainfo);
	}
	cout << "Insertion finished" << endl;
	db.set_position(position);
}

/*
 * Return:
 * 0 : failed
 * 1 : OK
 * 2 : cancelled by user
 */
int datalite::show_unique_database_fields(const string& sql){
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	int result = 0;

	if (!rc){
		sqlite3_stmt *res;
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK) {

			int step = sqlite3_step(res);
			int option;
			do{

				int i = 0;
				while (step == SQLITE_ROW && i < 10) {
					char * text_c = (char *) sqlite3_column_text(res, 0);
					string text(text_c);

					if(_found_links.find(text) == _found_links.end()){
						cout << text << endl;
						_found_links.insert(text);
					}
					step = sqlite3_step(res);
					++i;
				}
				if(i > 5){
					cout << "Do you want more links?\n"
						"1.- Yes\n"
						"2.- No" << endl;
					cin >> option;
				} else
					option = 1;
				
			}while(option == 1 && step == SQLITE_ROW);
			result = option;
		}
		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return result;
}

string datalite::get_one_new(int64_t position){

	string result;
	sqlite3 *sq;
	int rc = sqlite3_open(_file.c_str(), &sq);
	string link;

	if (!rc){
		
		sqlite3_stmt *res;
		string sql = "SELECT TITLE, LINK, DESCRIPTION, MARKED FROM NEWS WHERE rowid=" + to_string(position);
		rc = sqlite3_prepare_v2(sq, sql.c_str(), -1, &res, 0);

		if (rc == SQLITE_OK) {
			sqlite3_step(res);
			char * retri = (char *) sqlite3_column_text(res, 3);
			string text(retri);
			if(text[0]== '1')
				cout << "\n MARKED(*)" << endl;
			
			retri = (char *) sqlite3_column_text(res, 0);

			if(retri != NULL) link = string(retri);
			result += "\n" + link + "\n\n";
			retri = (char *) sqlite3_column_text(res, 1);

			if(retri != NULL) link = string(retri);
			result += link + "\n\n";
			retri = (char *) sqlite3_column_text(res, 2);

			if(retri != NULL) link = string(retri);
			result += link;
		}

		sqlite3_finalize(res);
	}
	sqlite3_close(sq);
	return result;
}
